﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.Drawing;
using Noris.Tools.FrxEditor.Editor;
using System.Windows.Forms;
using Noris.Tools.FrxEditor.Components;
using System.ComponentModel;
using Noris.Tools.FrxEditor.Forms;
using Noris.Tools.FrxEditor.ComponentsLayer;
using Noris.Schedule.Support;
using Noris.Schedule.Support.Parser;

namespace Noris.Tools.FrxEditor.Data
{
	/// <summary>
	/// Datová obálka editovaného dokumentu. 
	/// Ta se ukládá do XML dat, tu lze renderovat do sestavy.
	/// Slouží jako zdroj/cíl dat pro editaci.
	/// </summary>
	public partial class ReportDocument : IRevertContainer
	{
		#region Konstrukce
		/// <summary>
		/// Konstruktor
		/// </summary>
		public ReportDocument()
		{
            this.Properties = new ReportDocumentProperties(this);
			this.ReportGrid = new ReportGrid();
            this.Reverter = new Reverter(this);
            this._InitData(DocumentNewPresetMode.OnlyRootBand);
		}
		#endregion
        #region Logická data dokumentu: ReportBand a ReportComponent, jejich přidávání a vyhledávání
        /// <summary>
        /// Vymaže obsah dokumentu.
        /// V dokumentu vytvoří pouze RootBand (bez něj dokument nelze používat), nic dalšího.
        /// Tato metoda je datová = nevolá žádný RefreshUI() ani ReDraw().
        /// </summary>
        public void ClearAll()
        {
            this._InitData(DocumentNewPresetMode.OnlyRootBand);
        }
        /// <summary>
        /// Vymaže obsah dokumentu.
        /// V dokumentu vytvoří pouze RootBand (bez něj dokument nelze používat).
        /// Podle parametru createStandardBands může vytvořit i základní tři bands: PageHeader, Detail a PageFooter.
        /// Tato metoda je datová = nevolá žádný RefreshUI() ani ReDraw().
        /// </summary>
        /// <param name="presetMode">Které Bands přidat po Clear</param>
        public void ClearAll(DocumentNewPresetMode presetMode)
        {
            this._InitData(presetMode);
        }
        /// <summary>
        /// Inicializuje data
        /// </summary>
        /// <param name="presetMode">Které Bands přidat po Clear</param>
        private void _InitData(DocumentNewPresetMode presetMode)
        {
            this.Properties.Clear();

            this.RootBand = null;
            this.BandDict = new Dictionary<int, ReportBand>();
            this.ComponentDict = new Dictionary<int, ReportComponent>();
            this.DocumentFile = null;
            this.Properties.Clear();
            ReportBand.ResetId();
            ReportComponent.ResetId();

            switch (presetMode)
            {
                case DocumentNewPresetMode.OnlyRootBand:
                    this._InitRootBand();
                    break;
                case DocumentNewPresetMode.StandardReportBand:
                    this._InitRootBand();
                    this._InitStandardBands();
                    break;
            }

            this.Reverter.Clear();
        }
        /// <summary>
        /// Vytvoří RootBand
        /// </summary>
        private void _InitRootBand()
        {
            this.RootBand = new ReportBand(this);
            this.RootBand.HeaderText = "Report";
            this.RootBand.HeaderBackColor = Color.Azure;
            this.RootBand.LogicalHeight = 100f;
            this.BandDict.Add(this.RootBand.BandId, this.RootBand);
        }
        /// <summary>
        /// Vytvoří tři základní bands: PageHeader, Detail a PageFooter.
        /// </summary>
        private void _InitStandardBands()
        {
            ReportBand header = new ReportBand(this) { OwnerBandId = this.RootBand.BandId, HeaderText = "PageHeader", HeaderBackColor = Color.LightCyan, LogicalHeight = 40f };
            this.AddBand(header);

            ReportBand detail = new ReportBand(this) { OwnerBandId = this.RootBand.BandId, HeaderText = "Detail", HeaderBackColor = Color.LightYellow, LogicalHeight = 20f };
            this.AddBand(detail);

            ReportBand footer = new ReportBand(this) { OwnerBandId = this.RootBand.BandId, HeaderText = "PageFooter", HeaderBackColor = Color.Wheat, LogicalHeight = 40f };
            this.AddBand(footer);
        }
        /// <summary>
        /// Vlastnosti sestavy
        /// </summary>
        public ReportDocumentProperties ReportProperties { get { return this.Properties; } }
        /// <summary>
		/// Kořenový Band = reprezentuje celou sestavu v její komplexnosti.
		/// Typicky obsahuje sadu vnořených Bands.
		/// Tento objekt je vytvářen vždy automaticky jeden pro celou sestavu.
		/// </summary>
		public ReportBand RootBand { get; private set; }
		/// <summary>
		/// Přidá do dokumentu řadu prvků Band.
		/// Pokud tento nemá určen OwnerBand (jeho OwnerBandId == 0), pak jako jeho ParentBand uvede sebe (this..RootBand.BandId).
		/// Do Bandu vždy uvede jeho Document = this.
		/// </summary>
		/// <param name="bands"></param>
		public void AddBands(params ReportBand[] bands)
		{
			foreach (ReportBand band in bands)
				this.AddBand(band);
		}
		/// <summary>
		/// Přidá do dokumentu daný Band.
		/// Pokud tento nemá určen OwnerBand (jeho OwnerBandId == 0), pak jako jeho ParentBand uvede sebe (this..RootBand.BandId).
		/// Do Bandu vždy uvede jeho Document = this.
		/// Vrací číslo BandId vstupujícího objektu 
		/// (objekt může být do parametru předáván přes zásobník: AddBand(new ReportBand() { properties = values }), pak je to výhodné pro volajícího.
		/// </summary>
		/// <param name="band"></param>
		public int AddBand(ReportBand band)
		{
			if (this.BandDict.ContainsKey(band.BandId))
				throw new InvalidOperationException("Band with ID=" + band.BandId.ToString() + " already exists in document.");
            if (this.RootBand == null)
                this.RootBand = band;

            band.Document = this;
			if (band.OwnerBandId <= 0 && !Object.ReferenceEquals(band, this.RootBand))
				band.OwnerBandId = this.RootBand.BandId;
			this.BandDict.Add(band.BandId, band);
			return band.BandId;
		}
		/// <summary>
		/// Vrátí svazek komponent (Band) podle jeho ID.
		/// </summary>
		/// <param name="bandId"></param>
		/// <returns></returns>
		public ReportBand GetBand(int bandId)
		{
			return this.BandDict[bandId];
		}
        /// <summary>
        /// Zkusí najít svazek komponent (Band) podle jeho ID.
        /// </summary>
        /// <param name="bandId"></param>
        /// <param name="band">Nalezený svazek</param>
        /// <returns></returns>
        public bool TryGetBand(int bandId, out ReportBand band)
        {
            return this.BandDict.TryGetValue(bandId, out band);
        }
        /// <summary>
		/// Odebere daný svazek Band
		/// </summary>
		/// <param name="bandId"></param>
		private void RemoveBand(int bandId)
		{
		}
		/// <summary>
		/// Přidá do dokumentu řadu prvků ReportComponent.
		/// Do komponenty vždy uvede její Document = this.
		/// </summary>
		/// <param name="components"></param>
		public void AddComponents(params ReportComponent[] components)
		{
			foreach (ReportComponent component in components)
				this.AddComponent(component);
		}
		/// <summary>
		/// Přidá do dokumentu danou komponentu.
		/// Do komponenty vždy uvede její Document = this.
		/// </summary>
		/// <param name="component"></param>
        public int AddComponent(ReportComponent component)
        {
            return this._AddComponent(component, false);
        }
		/// <summary>
		/// Přidá do dokumentu danou komponentu.
		/// Do komponenty vždy uvede její Document = this.
		/// </summary>
		/// <param name="component"></param>
        /// <param name="addToEditorList">Přidat novou komponentu rovnou i do soupisu viditelných komponent.
        /// Ten běžně vzniká přípravou (this.PrepareDraw()), ale tuto přípravu při interaktivním přidávání komponent nespouštíme.</param>
		private int _AddComponent(ReportComponent component, bool addToEditorList)
		{
			if (this.ComponentDict.ContainsKey(component.ComponentId))
				throw new InvalidOperationException("Component with ID=" + component.ComponentId.ToString() + " already exists in document.");
			// component.Document = this;
			Int32? maxZOrder = ReportComponent.FindMaxZOrder(this.ComponentDict.Values);
			component.ZOrder = (maxZOrder.HasValue ? maxZOrder.Value + 1 : 1);
			this.ComponentDict.Add(component.ComponentId, component);
            if (addToEditorList)
                this.AddComponentToEditList(component);
			return component.ComponentId;
		}
        /// <summary>
        /// Předanou komponentu jednotlivě přidá do soupisu komponent pro zobrazování.
        /// Využívá se při interaktivním insertu nového objektu.
        /// </summary>
        /// <param name="component"></param>
        private void AddComponentToEditList(ReportComponent component)
        {
            List<ReportComponent> editorComponents = new List<ReportComponent>(this.EditorComponents);
            editorComponents.Add(component);
            this.EditorComponents = editorComponents;
        }
		/// <summary>
		/// Vrátí komponentu podle jejího ID.
        /// Pokud neexistuje, vyhodí se chyba.
		/// </summary>
		/// <param name="componentId"></param>
		/// <returns></returns>
		public ReportComponent GetComponent(int componentId)
		{
			return this.ComponentDict[componentId];
		}
		/// <summary>
		/// Odebere jednu danou komponentu.
        /// Neřeší se zápis do Undo registru, to má řešit volající !
		/// </summary>
		/// <param name="componentId"></param>
		public void RemoveComponent(int componentId)
		{
            this.ComponentDict.Remove(componentId);
		}
        /// <summary>
        /// Vlstnosti sestavy
        /// </summary>
        protected ReportDocumentProperties Properties;
		/// <summary>
		/// Slovník všech Bands (linearizovaný)
		/// </summary>
		protected Dictionary<int, ReportBand> BandDict;
		/// <summary>
		/// Slovník všech ReportComponent (linearizovaný)
		/// </summary>
		protected Dictionary<int, ReportComponent> ComponentDict;
		/// <summary>
		/// Objekt Gridu (vlastnosti gridu - druh, rozteče, tvorba souřadnic; nikoli jeho kreslení - to řeší Reportband)
		/// </summary>
        protected ReportGrid ReportGrid;
        /// <summary>
        /// Objekt Reverter. Řeší Undo + Redo, řeší jejich tvorbu a provádění.
        /// </summary>
        [Browsable(false)]
        public Reverter Reverter { get; private set; }
        #endregion
        #region Public property
        /// <summary>
        /// Záznamy, pro které se má report tisknout
        /// </summary>
        public IEnumerable<int> Records { get; set; }
        /// <summary>
        /// Výchozí písmo pro nově přidávané prvky.
        /// Nikdy není null.
        /// </summary>
        public Font DefaultFont
        {
            get
            {
                if (this._DefaultFont == null)
                    this._DefaultFont = ReportDocument.DefaultReportFont;
                return this._DefaultFont;
            }
            set { this._DefaultFont = value; }
        }
        private Font _DefaultFont;
        /// <summary>
        /// Výchozí font reportu, konstantní hodnota (vždy new()).
        /// </summary>
        public static Font DefaultReportFont { get { return new Font("Arial", 9f, FontStyle.Regular, GraphicsUnit.Point); } }
        #endregion
    }
    #region class ReportDocumentProperties : datové vlastnosti sestavy, jsou vyčleněné do izolované třídy
    /// <summary>
    /// ReportDocumentProperties : datové vlastnosti sestavy, jsou vyčleněné do izolované třídy
    /// </summary>
    public class ReportDocumentProperties
    {
        #region Konstrukce
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="document"></param>
        public ReportDocumentProperties(ReportDocument document)
        {
            this.Document = document;
        }
        #endregion
        #region Public properties
        /// <summary>
        /// Reference na dokument
        /// </summary>
        public ReportDocument Document { get; private set; }
        /// <summary>
        /// Šířka záhlaví v editoru, v pixelech
        /// </summary>
        public float EditorBandHeaderWidth { get; set; }
        /// <summary>
        /// Výška pravítka v editoru, v pixelech
        /// </summary>
        public float EditorHRulerHeight { get; set; }
        /// <summary>
        /// Šířka panelu vlastností v editoru, v pixelech
        /// </summary>
        public float EditorPropertyPanelWidth { get; set; }
        /// <summary>
        /// Výchozí šířka Bands
        /// </summary>
        public float DefaultBandHeaderWidth { get; set; }
        /// <summary>
        /// Souhrn šířek jednotlivých Bands, které jsou IsContainer.
        /// </summary>
        public List<float> BandVisualWidthList { get; set; }
        /// <summary>
        /// Pro selectování výběrem: Poměrná část komponenty, která musí být ve Frame, aby komponenta byla selectována
        /// </summary>
        public float SelectFrameRatio { get; set; }
        /// <summary>
        /// Barva pozadí
        /// </summary>
        public Color BackColor { get; set; }
        /// <summary>
        /// Název dokumentu, použije se při tisku
        /// </summary>
        public string DocumentName { get; set; }
        /// <summary>
        /// Velikost stránky včetně názvu (PageSizeInfo)
        /// </summary>
        public PageSizeInfo PageSize
        {
            get { return this._PageSize; }
            set { this._PageSize = value; this._RecalcSize(true); }
        }
        private PageSizeInfo _PageSize;
        /// <summary>
        /// Orientace papíru
        /// </summary>
        public PageOrientationType PageOrientation
        {
            get { return this._PageOrientation; }
            set { this._PageOrientation = value; this._RecalcSize(true); }
        }
        private PageOrientationType _PageOrientation;
        /// <summary>
        /// Velikost okrajů stránky
        /// </summary>
        public PaddingF PrintMargin
        {
            get { return this._PrintMargin; }
            set { this._PrintMargin = value; this._RecalcSize(true); }
        }
        private PaddingF _PrintMargin;
        /// <summary>
        /// Skutečná velikost papíru (včetně okrajů), odvozená z velikosti stránky (this.PageSize) a orientace (this.PageOrientation).
        /// Využitelná velikost je v this.PaperDocumentArea.
        /// </summary>
        public SizeF PaperSize { get; private set; }
        /// <summary>
        /// Rozměry a umístění dokumentu na stránce.
        /// Popisuje využitelná plochu.
        /// Je dána: velikostí stránky (this.PageSize), orientací (this.PageOrientation) a okraji (this.PrintMargin).
        /// </summary>
        public RectangleF PaperDocumentArea { get; private set; }
        /// <summary>
        /// Přepočte reálnou velikost stránky a podle požadavku ji vykreslí
        /// </summary>
        /// <param name="reDraw"></param>
        private void _RecalcSize(bool reDraw)
        {
            this.PaperSize = this.PageSize.GetSize(this.PageOrientation);
            this.PaperDocumentArea = this.PrintMargin.GetInnerArea(this.PaperSize);

            if (reDraw)
                this.Document.ReDraw();
        }
        #endregion
        #region Fyzická XML serializace : XmlSerializeTo(), XmlFillFrom()
        /// <summary>
        /// Zajistí serializaci všech svých hodnot do předaného XML elementu.
        /// Element vytváří nadřízená vrstva (ReportDocument), úkolem komponenty je uložit do XML elementu všechny své hodnoty.
        /// Každá třída ukládá své hodnoty, před tím volá base.XmlSerializeTo(node), aby předkové uložili svoje data.
        /// </summary>
        /// <param name="node"></param>
        public void XmlSerializeTo(XElement node)
        {
            node.SetAttributeValue("Type", this.Document.GetType().Name);
            node.SetAttributeValue("ClassNumber", Convertor.Int32ToString(1188));
            node.SetAttributeValue("BandVisualWidthList", Convertor.IEnumerableToString(this.BandVisualWidthList));
            node.SetAttributeValue("EditorBandHeaderWidth", Convertor.SingleToString(this.EditorBandHeaderWidth));
            node.SetAttributeValue("EditorHRulerHeight", Convertor.SingleToString(this.EditorHRulerHeight));
            node.SetAttributeValue("EditorPropertyPanelWidth", Convertor.SingleToString(this.EditorPropertyPanelWidth));
        }
        /// <summary>
        /// Načte do sebe veškerá data z XML nodu.
        /// </summary>
        /// <param name="node"></param>
        public virtual void XmlFillFrom(XElement node)
        {
            int classNumber = (Int32)Convertor.StringToInt32(ReportDocument.XmlGetXAttributeValue(node, "ClassNumber"));
        }
        /// <summary>
        /// Inicializace
        /// </summary>
        public void Clear()
        {
            this.EditorBandHeaderWidth = 100f;
            this.EditorHRulerHeight = 25f;
            this.EditorPropertyPanelWidth = 240f;
            this.BandVisualWidthList = new List<float>();
            this.SelectFrameRatio = 0.05f;
            this.DefaultBandHeaderWidth = 22f;
            this.BackColor = Color.White;

            this.DocumentName = "";
            this.PageSize = PageSizeInfo.A4;
            this.PageOrientation = PageOrientationType.Portrait;
            this.PrintMargin = new PaddingF(12.7f);
        }
        #endregion
    }
    #endregion
    #region class ParserSqlExpressionSetting : Generátor settingů pro SQL kód + Expressions
    /// <summary>
    /// ParserExpressionEditorSetting : Generátor settingů pro Expressions
    /// </summary>
    public static class ParserSqlExpressionSetting
    {
        #region Public static property, které vracejí settings
        /// <summary>
        /// ParserSetting pro čistý SqlEditor
        /// </summary>
        public static ParserSetting SqlSetting { get { return _GetSettingSqlExpression(false); } }
        /// <summary>
        /// ParserSetting pro SqlExpressionEditor
        /// </summary>
        public static ParserSetting SqlExpressionSetting { get { return _GetSettingSqlExpression(true); } }
        #endregion
        #region Konstanty
        /// <summary>
        /// Název segmentu, který obsahuje proměnnou: ExpVariable
        /// </summary>
        public const string EXP_VARIABLE = "ExpVariable";
        /// <summary>
        /// Název segmentu, který obsahuje SQL výraz
        /// </summary>
        public const string SQL_CODE = ParserDefaultSetting.SQL_CODE;
        /// <summary>
        /// String, který uvozuje proměnnou: «{
        /// </summary>
        public const string EXP_VAR_BEGIN = "«{";
        /// <summary>
        /// String, který zakončuje proměnnou: }»
        /// </summary>
        public const string EXP_VAR_END = "}»";
        #endregion
        #region Generátor settings pro GreenExpression
        /// <summary>
        /// Vrátí Settngs pro parsování SQL text, kde navíc může být použit výraz
        /// </summary>
        /// <returns></returns>
        private static ParserSetting _GetSettingSqlExpression(bool withExpression)
        {
            // Základem je SQL kód:
            ParserSetting setting = new ParserSetting("SqlCode", SQL_CODE);
            ParserSegmentSetting sqlSettings = Noris.Schedule.Support.Parser.ParserDefaultSetting.AddSettingMsSql(setting);

            if (withExpression)
            {   // Nad rámec SQL povolujeme VÝRAZY:
                ParserSegmentSetting segment = new ParserSegmentSetting(EXP_VARIABLE);
                segment.BeginWith = EXP_VAR_BEGIN;
                segment.EndWith = new string[] { EXP_VAR_END };
                segment.AddRtfCodes(ParserSegmentValueType.Text,
                                    new RtfItem[] { RtfItem.NewForeColor(Color.Blue), RtfItem.NewFont("Courier New CE"), RtfItem.NewFontStyle(RtfFontStyle.BoldOnly), RtfItem.NewProtect(true), RtfItem.NewHighlight(Color.FromArgb(186, 255, 255)) },
                                    new RtfItem[] { RtfItem.NewForeColor(Color.Empty), RtfItem.NewHighlight(Color.Empty) });
                setting.SegmentSettingAdd(segment);

                // Najdeme settingy pro jednotlivé segmenty SQL, a do vhodných segmentů přidáme možnost vložit navíc i naše proměnné:
                foreach (ParserSegmentSetting segm in setting.Segments.Where(s => (s.SegmentName == Noris.Schedule.Support.Parser.ParserDefaultSetting.SQL_CODE || s.SegmentName == Noris.Schedule.Support.Parser.ParserDefaultSetting.SQL_PARENTHESIS)))
                    _AddInnerSegmentName(segm, EXP_VARIABLE);
            }

            return setting;
        }
        /// <summary>
        /// Do dané deklarace segmentu přidá další jméno vnitřního segmentu (pokud tam ještě není).
        /// </summary>
        /// <param name="segm"></param>
        /// <param name="segmentName"></param>
        private static void _AddInnerSegmentName(ParserSegmentSetting segm, string segmentName)
        {
            if (segm == null || String.IsNullOrEmpty(segmentName)) return;
            if (segm.InnerSegmentsNames == null)
                segm.InnerSegmentsNames = new string[] { segmentName };
            else
            {
                List<string> names = new List<string>(segm.InnerSegmentsNames);
                if (!names.Contains(segmentName))
                {
                    names.Add(segmentName);
                    segm.InnerSegmentsNames = names.ToArray();
                }
            }
        }
        /// <summary>
        /// Do dané deklarace segmentu přidá další položku segm.RtfSet, anebo přepíše stávající položku se shodným typem (RtfItem.ItemType).
        /// </summary>
        /// <param name="segm"></param>
        /// <param name="rtfItem"></param>
        private static void _AddRtfSet(ParserSegmentSetting segm, RtfItem rtfItem)
        {
            if (segm == null || rtfItem == null) return;

            IEnumerable<ParserSegmentRtfCodePair> codePairs = segm.AllRtfCodePairs;
            foreach (ParserSegmentRtfCodePair codePair in codePairs)
            {
                if (codePair.CodeSet == null)
                    codePair.CodeSet = new RtfItem[] { rtfItem };
                else
                {
                    List<RtfItem> items = new List<RtfItem>(codePair.CodeSet);
                    int index = items.FindIndex(i => (i.ItemType == rtfItem.ItemType));
                    if (index >= 0)
                        items[index] = rtfItem;
                    else
                        items.Add(rtfItem);
                    codePair.CodeSet = items.ToArray();
                }
            }
        }
        /// <summary>
        /// Do dané deklarace segmentu přidá další položku segm.RtfReset, anebo přepíše stávající položku se shodným typem (RtfItem.ItemType).
        /// </summary>
        /// <param name="segm"></param>
        /// <param name="rtfItem"></param>
        private static void _AddRtfReset(ParserSegmentSetting segm, RtfItem rtfItem)
        {
            if (segm == null || rtfItem == null) return;

            IEnumerable<ParserSegmentRtfCodePair> codePairs = segm.AllRtfCodePairs;
            foreach (ParserSegmentRtfCodePair codePair in codePairs)
            {
                if (codePair.CodeReset == null)
                    codePair.CodeReset = new RtfItem[] { rtfItem };
                else
                {
                    List<RtfItem> items = new List<RtfItem>(codePair.CodeReset);
                    int index = items.FindIndex(i => (i.ItemType == rtfItem.ItemType));
                    if (index >= 0)
                        items[index] = rtfItem;
                    else
                        items.Add(rtfItem);
                    codePair.CodeReset = items.ToArray();
                }
            }
        }
        #endregion
    }
    #endregion
    #region enums
    /// <summary>
    /// Režim předvyplnění Bandů reportu po provedení Clear()
    /// </summary>
    public enum DocumentNewPresetMode
    {
        /// <summary>
        /// Nic nepřidávat, soupis Bands bude úplně prázdný, RootBand taky.
        /// Do RootBand se následně přidá první přidávaný Band.
        /// </summary>
        None,
        /// <summary>
        /// Přidat pouze první Band = přidá se do RootBand, popisuje Report.
        /// </summary>
        OnlyRootBand,
        /// <summary>
        /// Přidat RootBand + 3 datové bandy: Header, Body, Footer. Standardní pro editaci.
        /// </summary>
        StandardReportBand
    }
    /// <summary>
    /// Orientace stránky
    /// </summary>
    public enum PageOrientationType
    {
        /// <summary>
        /// Výchozí (portrait?)
        /// </summary>
        Default = 0,
        /// <summary>
        /// Na šířku
        /// </summary>
        Landscape = 1,
        /// <summary>
        /// Na výšku
        /// </summary>
        Portrait = 2
    }
    /// <summary>
    /// Velikost stránky
    /// </summary>
    public class PageSizeInfo
    {
        #region Public instanční property
        /// <summary>
        /// Index velikosti
        /// </summary>
        public int Index { get; private set; }
        /// <summary>
        /// Název velikosti
        /// </summary>
        public string Name { get; private set; }
        /// <summary>
        /// Velikost v milimetrech
        /// </summary>
        public SizeF Size { get; private set; }
        /// <summary>
        /// Klin sebe sama.
        /// Tento typ je class, tedy pokud chceme pracovat se svojí kopií, potřebujeme klon.
        /// </summary>
        public PageSizeInfo Clone { get { return (PageSizeInfo)this.MemberwiseClone(); } }
        #endregion
        #region Generátor instancí (as enum)
        public static PageSizeInfo Default { get { return new PageSizeInfo(0, "Default", 210, 297); } }
        public static PageSizeInfo Letter85 { get { return new PageSizeInfo(1, "Letter 8 1/2 x 11 in", 215.9f, 279.4f); } }
        public static PageSizeInfo LetterSmall85 { get { return new PageSizeInfo(2, "Letter Small 8 1/2 x 11 in", 215.9f, 279.4f); } }
        public static PageSizeInfo Tabloid11 { get { return new PageSizeInfo(3, "Tabloid 11 x 17 in", 279.40f, 431.80f); } }
        public static PageSizeInfo Ledger { get { return new PageSizeInfo(4, "Ledger 17 x 11 in", 431.80f, 279.40f); } }
        public static PageSizeInfo Legal85 { get { return new PageSizeInfo(5, "Legal 8 1/2 x 14 in", 215.90f, 355.60f); } }
        public static PageSizeInfo Statement55 { get { return new PageSizeInfo(6, "Statement 5 1/2 x 8 1/2 in", 139.70f, 215.90f); } }
        public static PageSizeInfo Executive72 { get { return new PageSizeInfo(7, "Executive 7 1/4 x 10 1/2 in", 184.15f, 266.70f); } }
        public static PageSizeInfo A3 { get { return new PageSizeInfo(8, "A3 297 x 420 mm", 297.00f, 420.00f); } }
        public static PageSizeInfo A4 { get { return new PageSizeInfo(9, "A4 210 x 297 mm", 210.00f, 297.00f); } }
        public static PageSizeInfo A4small { get { return new PageSizeInfo(10, "A4 Small 210 x 297 mm", 210.00f, 297.00f); } }
        public static PageSizeInfo A5 { get { return new PageSizeInfo(11, "A5 148 x 210 mm", 148.00f, 210.00f); } }
        public static PageSizeInfo B4 { get { return new PageSizeInfo(12, "B4 250 x 354 mm", 250.00f, 354.00f); } }
        public static PageSizeInfo B5 { get { return new PageSizeInfo(13, "B5 182 x 257 mm", 182.00f, 257.00f); } }
        public static PageSizeInfo Folio85 { get { return new PageSizeInfo(14, "Folio 8 1/2 x 13 in", 215.90f, 330.20f); } }
        public static PageSizeInfo Quarto { get { return new PageSizeInfo(15, "Quarto 215 x 275 mm", 215.00f, 275.00f); } }
        public static PageSizeInfo Size10x14 { get { return new PageSizeInfo(16, "10 x 14 in", 254.00f, 355.60f); } }
        public static PageSizeInfo Size11x17 { get { return new PageSizeInfo(17, "11 x 17 in", 279.40f, 431.80f); } }
        /// <summary>
        /// Konstruktor je zatím privátní
        /// </summary>
        /// <param name="index"></param>
        /// <param name="name"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        private PageSizeInfo(int index, string name, float width, float height)
        {
            this.Index = index;
            this.Name = name;
            this.Size = new SizeF(width, height);
        }
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return this.Name + " (" + this.Index.ToString() + ")";
        }
        /// <summary>
        /// Vrátí velikost papíru při jeho dané orientaci
        /// </summary>
        /// <param name="pageOrientation"></param>
        /// <returns></returns>
        public SizeF GetSize(PageOrientationType pageOrientation)
        {
            switch (pageOrientation)
            {
                case PageOrientationType.Landscape:
                    return new SizeF(this.Size.Height, this.Size.Width);   // Obrácená orientace Width - Height
                default:
                    return new SizeF(this.Size.Width, this.Size.Height);   // Standardní orientace Width - Height
            }
        }
        #endregion
        #region Reflexí získaný soupis property, vyhledání podle indexu
        /// <summary>
        /// Najde a vrátí velikost stránky podle daného indexu
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public static PageSizeInfo GetPageSize(int index)
        {
            return GetPageSize(index, 0);
        }
        /// <summary>
        /// Najde a vrátí velikost stránky podle daného indexu
        /// </summary>
        /// <param name="index"></param>
        /// <param name="defaultIndex"></param>
        /// <returns></returns>
        public static PageSizeInfo GetPageSize(int index, int defaultIndex)
        {
            PageSizeInfo info = AllList.FirstOrDefault(p => p.Index == index);
            if (info == null)
                info = AllList.FirstOrDefault(p => p.Index == defaultIndex);
            return info.Clone;
        }
        /// <summary>
        /// Pole všech typů velikostí.
        /// Obsahuje trvale uskladněné originály, které se ven nevydávají.
        /// Vydávají se pouze kopie = Clone (jde o class).
        /// </summary>
        private static List<PageSizeInfo> AllList
        {
            get
            {
                if (_AllList == null)
                {
                    List<PageSizeInfo> inList = new List<PageSizeInfo>();
                    System.Reflection.PropertyInfo[] piList = typeof(PageSizeInfo).GetProperties(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
                    foreach (System.Reflection.PropertyInfo pi in piList)
                    {
                        inList.Add((PageSizeInfo)pi.GetGetMethod().Invoke(null, new object[0]));
                    }
                    inList.Sort(PageSizeInfo.CompareByIndex);
                    _AllList = inList;
                }
                return _AllList;
            }
        }
        private static List<PageSizeInfo> _AllList;
        private static int CompareByIndex(PageSizeInfo a, PageSizeInfo b)
        {
            return a.Index.CompareTo(b.Index);
        }
        #endregion
    }
    #endregion
}
